package com.googlecode.bizyap.core;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.googlecode.bizyap.core.logging.Logger;
import com.googlecode.bizyap.core.logging.LoggerFactory;


/**
 *
 * @author mehmet.dogan
 * this class is not thread safe!
 */

class Configuration {

	private static final Logger log = LoggerFactory.getLogger(Configuration.class);

	private final XPath xpath = XPathFactory.newInstance().newXPath();
	private DocumentBuilder builder = null;

	private final String confFileName;
	private volatile Set<Application> applications = new HashSet<Application>();
	private volatile String ssoLoginURL = null;
	private volatile String ssoLogoutURL = null;
	private volatile String ssoAuthURL = null;
	private volatile String loggerType = null;
	private volatile long confFileLastModified = 0L;

	Configuration(String confFilename){
		super();
		if(confFilename==null){
			throw new NullPointerException("Configuration file name can not ne NULL!");
		}

		try{
			builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		}
		catch (Exception e) {
			log.error("ERROR ON INSTANTIATING DOCUMENT BUILDER .. " + e.getMessage());
		}
		this.confFileName = confFilename;
	}

	boolean readConfiguration() throws Exception {
		log.debug("reading configuration file: " + confFileName);
		File confXMLFile = readCatalinaConf();
		if(confXMLFile==null){
			confXMLFile = readUserDir();
		}

		InputStream confStream = null;
		if(confXMLFile==null){
			log.warn("NO CONFIGURATION FILE IS FOUND!");
			log.warn("LOADING DEFAULT CONFIGURATION...");
			confStream = getClass().getClassLoader().getResourceAsStream(Globals.CONFIGURATION_FILE);
			confFileLastModified = 0L;
		}
		else {
			if(confFileLastModified < confXMLFile.lastModified()){
				log.info("found new configuration file.... " + confXMLFile.getAbsolutePath());
				confStream = new BufferedInputStream(new FileInputStream(confXMLFile));
				confFileLastModified = confXMLFile.lastModified();
			}
			else {
				return false;
			}
		}

		if(confStream==null){
			log.error("agent configuration file caon not be read!");
			throw new IOException("agent configuration file can not be read! - " + confFileName);
		}

		parseConfiguration(confStream);
		confStream.close();
		return true;
	}

	private File readCatalinaConf(){
		String catalinaHome = System.getProperty("catalina.home");
		log.debug("catalina home: " + catalinaHome);
		File catalinaConfDir = new File(catalinaHome + File.separator + "conf");
		if(catalinaConfDir.exists() && catalinaConfDir.isDirectory()) {
			log.debug("found catalina conf directory... : " + catalinaConfDir.getAbsolutePath());
			File confXML = new File(catalinaConfDir, confFileName);
			if(confXML.exists()){
				return confXML;
			}
		}
		log.warn("could not find " + confFileName + " in catalina conf directory!");
		return null;
	}

	private File readUserDir(){
		String userDir = System.getProperty("user.dir");
		log.debug("user dir: " + userDir);
		File userDirFolder = new File(userDir);
		if(userDirFolder.exists() && userDirFolder.isDirectory()) {
			log.debug("found user directory... : " + userDirFolder.getAbsolutePath());
			File confXML = new File(userDirFolder, confFileName);
			if(confXML.exists()){
				return confXML;
			}
		}
		log.info("could not find " + confFileName + " in user directory!");
		return null;
	}

	private void parseConfiguration(InputStream stream) throws SAXException, IOException, XPathExpressionException {
		log.info("parsing configuration file: " + confFileName);
		Document document = builder.parse(stream);

		String tempSsoLoginURL = (String)xpath.evaluate("/SSO/sso-login-url", document, XPathConstants.STRING);
		if(tempSsoLoginURL!=null && !"".equals(tempSsoLoginURL)){
			ssoLoginURL = tempSsoLoginURL.trim();
		}

		String tempSsoLogoutURL = (String)xpath.evaluate("/SSO/sso-logout-url", document, XPathConstants.STRING);
		if(tempSsoLogoutURL!=null && !"".equals(tempSsoLogoutURL)){
			ssoLogoutURL = tempSsoLogoutURL.trim();
		}

		String tempSsoAuthURL = (String)xpath.evaluate("/SSO/sso-auth-url", document, XPathConstants.STRING);
		if(tempSsoAuthURL!=null && !"".equals(tempSsoAuthURL)){
			ssoAuthURL = tempSsoAuthURL.trim();
		}
		
		String tempLoggerType = (String)xpath.evaluate("/SSO/logger-type", document, XPathConstants.STRING);
		if(tempLoggerType!=null && !"".equals(tempLoggerType)){
			loggerType = tempLoggerType.trim();
		}

		Set<Application> temp = new HashSet<Application>();
		NodeList nodeList = (NodeList)xpath.evaluate("/SSO/applications/application", document, XPathConstants.NODESET);
		int size = nodeList.getLength();
		for (int i = 0; i < size; i++) {
			Node application = nodeList.item(i);
			NodeList childNodes = application.getChildNodes();

			int childSize = childNodes.getLength();
			String context = null;
			String logoutPattern = null;
			String authClassName = null;
			Set<String> privatePatterns = null;

			for (int j = 0; j < childSize; j++) {
				Node node = childNodes.item(j);
				if(node.getNodeType()!=Node.ELEMENT_NODE) continue;

				if("context-path".equals(node.getNodeName())){
					context = node.getTextContent().trim();
				}
				else if("logout-pattern".equals(node.getNodeName())){
					logoutPattern = node.getTextContent().trim();
				}
				else if("auth-class".equals(node.getNodeName())){
					authClassName = node.getTextContent().trim();
				}
				else if("secure-patterns".equals(node.getNodeName())){
					NodeList patternNodes = node.getChildNodes();
					int patternSize = patternNodes.getLength();
					privatePatterns = new HashSet<String>(patternSize);

					for (int k = 0; k < patternSize; k++) {
						Node patternNode = patternNodes.item(k);
						if(patternNode.getNodeType()!=Node.ELEMENT_NODE) continue;
						String pattern = patternNode.getTextContent().trim();
						privatePatterns.add(pattern);
						log.info("ctx: " + context + ", pattern: " + pattern);
					}
				}
			}

			try {
				temp.add(new Application(context, logoutPattern, privatePatterns, authClassName));
			} catch (Exception e) {
				// SSO Application can not be created, so add an invalid application..
				temp.add(new Application(context, false));
				log.error("while creating SSO Application... [" + context + "]", e);
			}
		}
		applications = temp;
	}

	Set<Application> getApplications(){
		return applications;
	}

	String getSSOLoginUrl(){
		return ssoLoginURL;
	}

	String getSSOLogoutUrl(){
		return ssoLogoutURL;
	}

	String getSSOAuthUrl(){
		return ssoAuthURL;
	}

	String getLoggerType() {
		return loggerType;
	}
}
